/*********************************************
 * Copyright (c) 2009 ICBC.
 * All rights reserved.
 * Created on 2010-6-10 下午04:38:12
 * 
 * Contributors:
 *     ctp - initial implementation
 *********************************************/
package com.icbc.ctpdemo.signin;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.xml.DOMConfigurator;

import com.icbc.cte.base.CTEConstance;
import com.icbc.cte.base.CTEException;
import com.icbc.cte.base.Settings;
import com.icbc.cte.logging.Log;
import com.icbc.cte.logging.LogFactory;
import com.icbc.ctp.channel.ChannelContext;
import com.icbc.ctp.channel.ModuleInfoLogger;
import com.icbc.ctp.channel.http.ioc.WebApplicationContext;
import com.icbc.ctp.core.exception.ContextEvaluateException;
import com.icbc.ctp.core.factory.OpAssemblyFactory;
import com.icbc.ctp.core.impl.Operation;
import com.icbc.ctp.framework.runtime.ApplicationContext;
import com.icbc.ctp.framework.runtime.J2EEHost;
import com.icbc.ctp.ioc.ConstructorException;
import com.icbc.ctp.ioc.FactortInitialException;
import com.icbc.ctp.ioc.ModulesException;
import com.icbc.ctp.ioc.context.ApplicationContextException;
import com.icbc.ctp.ioc.util.PropertyConfigurer;
import com.icbc.ctp.ioc.xmlconfig.FileSystemXmlApplicationContext;
import com.icbc.ctp.util.file.FileUtil;
import com.icbc.ctp.utility.StringUtil;

/**
 * 基于CTP平台的初始化类.
 * 
 * <pre>
 * 对CTP平台进行初始化，完成一些系统的加载.主要内容包括：
 * 	 1.完成日志文件的加载，其中getLogFilePath获取日志文件的路径，默认为公共构件包下的log4j.xml文件;
 *   2.完成IOC模块的加载，主要对配置在modules等文件中的对象进行初始化加载(原有的多语言对象初始化都在IOC配置文件中加载)
 *   3.执行初始化OP交易，完成serverCtx相关数据的家族，如果是新平台的OP，引擎执行完毕以后，需要用户执行
 *     updateServerCtx方法，将执行后的channelCtx相关数据，写入到serverCtx对象中.如果是旧
 *     平台，由于context是上下链关系，所以不用执行此方法.
 *   4.用户自定义内容的初始化，可以在userInitExtend方法中进行扩展，该方法在IOC和initOpExcute方法
 *     执行完毕后执行,默认旧平台调用initSsic方法初始化统一认证，如果需要可以将initSsic中的注释内容打开，
 *     编译需要统一认证提供ssic.jar，默认CTP平台不提供此jar包.
 *   
 *  对开发人员主要需要修改和扩展的内容为：
 *   1.INIT_OP成员变量的值，初始化OP交易的名称，默认为loadserverdataop，如有不同，请修改;
 *     OP的名称全部为小写，升级的时候已经转换，CTP平台对OP交易名大小写不区分.
 *   2.isNewProject是否为新平台，如果是全新的CTP平台则为true，如果是原有CTP平台升级上来的，请修改为false；
 *   3.对于全新平台，请在初始化OP交易后，扩展完成updateServerCtx方法，将一些执行后的数据，写入serverCtx中，具体可以
 *     参考如下：
 *     	IContext serverCtx = channelCtx.getServerContext(); //获取serverCtx对象.
 * 		Object flag = channelCtx.getValueAt(“flag”); //获取执行后flag的值
 * 		serverCtx.setValueAt(“flag”, flag); //将flag的值写入到serverCtx对象中.
 *   4.对于部分应用，在平台完成加载后，可以在userInitExtend方法中添加自己的部分代码.
 *   5.如果应用需要启动SSIC，则将initSsic方法内容注释取消
 *   6.对于使用旧平台AUTH4CTP.jar的项目，注意将initAuth方法注释取消.
 *   7.注意修改doPost中的response.setCharacterEncoding(&quot;utf-8&quot;);response.setContentType(&quot;text/html; charset=utf-8&quot;)方法中的编码参数
 * </pre>
 * 
 */
public class CTPStartupServlet extends HttpServlet {
	private static final long serialVersionUID = 7307293530793706029L;
	private String INIT_OP = "loadserverdataop";
	private boolean isNewProject = true;

	private FileSystemXmlApplicationContext appContext = null;
	private Log ctpLog;
	
	private static final int refreshLogConfigInteval = 600000; //刷新log4j.xml的时间间隔，此处设置为10分钟

	/**
	 * 执行初始化交易.
	 * 
	 * <pre>
	 *    对OP交易进行初始化，注意如果是升级的兼容交易，由于Context在执行的时候，自动组成上小链的关系，
	 *  交易一般执行完毕后，serverCtx里面是有数据的.
	 *    但是对于新平台，如果Context没有链关系，执行完毕后的channelCtx数据一般需要通过updateServerCtx
	 * 方法，将数据通过代码的方式写入到serverCtx对象中.
	 * 
	 * </pre>
	 * 
	 * @throws Exception
	 */
	private void initOpExcute() throws Exception {
		Operation op = null;
		try {
//			op = ((OpAssemblyFactory) appContext.getModule("operationFactory"))
//					.createOperation(INIT_OP);
			ChannelContext channelCtx = new ChannelContext();
//			op.doExecute(channelCtx);
			if (isNewProject) {
				updateServerCtx(channelCtx);
			}
			userInitExtend();
		} catch (Exception e) {
			throw e;
		} finally {
			if (op != null) {
				op.close();
			}
		}
	}

	/**
	 * 新平台将初始化OP执行的数据通过代码的方式写入到serverCtx对象中.
	 * 
	 * <pre>
	 *   一般对于全新的CTP平台需要扩展此方法.
	 * </pre>
	 * 
	 * @param channelCtx
	 * @throws ContextEvaluateException
	 */
	protected void updateServerCtx(ChannelContext channelCtx)
			throws ContextEvaluateException {
		ChannelContext.getServerContext().setValueAt("authFlag",
				CTEConstance.getEnvProperty("authFlag"));
		ChannelContext.getServerContext().setValueAt("enableSSIC",
				CTEConstance.getEnvProperty("enableSSIC"));
		// IContext serverCtx = channelCtx.getServerContext();
		// Object flag = channelCtx.getValueAt("flag");
		// serverCtx.setValueAt("flag", flag);
	}

	/**
	 * 平台初始化后用户自定义内容扩展.
	 * 
	 * <pre>
	 *  在CTP平台完成初始化后，应用系统自行的相关内容请在此处编写.旧平台一般有统一认证初始化，
	 *  如果有，可以将initSsic方法中的内容注释取消.
	 * </pre>
	 * 
	 * @throws Exception
	 */
	protected void userInitExtend() throws Exception {
		if (!isNewProject) {
			initSsic();
			initAuth();
		}
		// 此后可以扩展自己的内容.

	}

	private void initAuth() throws Exception {
		// 对于部分使用AUHT4CTP.jar的应用，原有的代码中可能有以下内容初始化系统管理，如有，请打开此注释，否则可删除.
		// AuthInfoModelInit ai = new AuthInfoModelInit();
		// ai.init();
	}

	/**
	 * 初始化化统一认证公私钥.
	 * 
	 * <pre>
	 *   默认CTP平台关闭此方法，如果应用系统需要，可以将注释取消，但是依赖的ssic.jar请找相应的统一认证
	 *  平台提供.
	 * @throws Exception
	 */
	private void initSsic() throws Exception {
		// try {
		// if (Settings.getSettings().getValueAt("enableSSIC").equals("true")) {
		// // 统一认证初始化
		// KeyedCollection ssicKoll = null;
		// ssicKoll = (KeyedCollection) Context.getRoot().getElementAt(
		// "ssicData");
		//
		// String KeyPath = (String) ssicKoll.getValueAt("SSICKeyPath");
		// KeyPath = CTEConstance.genFilePath(KeyPath);
		//
		// String privKeyPasswd = (String) ssicKoll
		// .getValueAt("privKeyPasswd");
		// String siteService = (String) ssicKoll
		// .getValueAt("SSICServiceName");
		// String serverService = (String) ssicKoll
		// .getValueAt("SSICServerService");
		//
		// SSICService ssic = new SSICService(serverService);
		// SSICService ssictest = new SSICService(siteService);
		//
		// ssictest.initialize(privKeyPasswd, KeyPath);
		// ssic.initialize(KeyPath);
		//
		// ssicKoll.setValueAt("publicKey", ssic.getPublicKey());
		// ssicKoll.setValueAt("privateKey", ssictest.getPrivateKey());
		// }
		// } catch (Exception ex) {
		// ctpLog.error("ctpdemo->CTPStartupServlet", "初始化统一认证错误", ex);
		// throw new Exception("初始化统一认证错误", ex);
		// }

	}

	/**
	 * 默认Servlet第一次加载调用类.
	 * 
	 * <pre>
	 *  1.首先获取log4j.xml文件中相关环境变量的值，如果不存在，则一般log4j.xml里面配置文件的路径
	 *    是需要应用系统进行配置，并且需要修改多处配置文件的路径.如果定义了环境变量，log4j.xml文件可以
	 *    通过环境变量的配置方式，投产的时候，就不需要修改log4j.xml文件，直接修改web.xml文件此
	 *    参数的值，但是在开发的时候，一定不要把log4j.xml中日志文件路径输出到应用自己的目录下，否则WAS
	 *    会监控到应用目录变更，每次都重新发布应用，影响开发效率;
	 *  2.直接调用initSystem方法，所有的CTP平台初始化都通过此方法实现.
	 * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		// 用于设置生成日志路径的环境变量
		WebApplicationContext.setServletContext(config.getServletContext());
		J2EEHost.initLog(config.getServletContext());
		try {
			initSystem();
		} catch (Exception e) {
			if (ctpLog != null) {
				ctpLog
						.error("ctpdemo->CTPStartupServlet", "系统初始化init方法执行失败",
								e);
			}
			log("系统init初始化失败", e);
		}
	}

	/**
	 * 获取dse.ini文件的路径.
	 * 
	 * <pre>
	 *   对于升级应用，建议dse.ini文件在WEB-INF/config目录下，因为平台内部有些功能获取
	 *  相对目录就是dse.ini文件所在的目录，所以此目录非常重要.
	 *   对于全新的应用，也需要调用此方法，但是该目录下的dse.ini文件可以不存在，因为有些交易的
	 *  目录还是需要有一个相对目录.
	 * @return
	 */
	private String getDseFilePath() {
		String separator = System.getProperty("file.separator");
		StringBuffer buf = new StringBuffer(getServletConfig()
				.getServletContext().getRealPath(""));
		if(!StringUtil.isEndsWithFileSysSep(buf.toString())){
			buf.append(separator);
		}
		buf.append("WEB-INF");
		buf.append(separator);
		buf.append("config");
		buf.append(separator);
		buf.append("dse.ini");
		return buf.toString();
	}

	/**
	 * 获取构件包所在的路径.
	 * 
	 * <pre>
	 *  CTP平台默认构件包的路径，此路径一般是不允许变更的.
	 * @return
	 */
	private String getContributionsPath() {
		String separator = System.getProperty("file.separator");
		StringBuffer buf = new StringBuffer(getServletConfig()
				.getServletContext().getRealPath(""));
		if(!StringUtil.isEndsWithFileSysSep(buf.toString())){
			buf.append(separator);
		}
		buf.append("WEB-INF"); 
		buf.append(separator);
		buf.append("config");
		buf.append(separator);
		buf.append("contributions");
		return buf.toString();
	}

	/**
	 * 从env.properties文件中获取日志文件log4j.xml中logPath的属性路径.
	 * 
	 * <pre>
	 * 默认该值获取的路径是应用的WEB-INF/config/contributions/common/env.properties文件中的logPath变量的值.
	 * 由于开发阶段日志写入此目录，会导致WAS不断的重新启动，所以建议此路径的值不要设置为WEB应用目录下的文件夹
	 * </pre>
	 * 
	 * @return
	 * @throws IOException
	 */
	public String getLogFilePropertiesVariableValue() throws IOException {
		String separator = System.getProperty("file.separator");
		StringBuffer buf = new StringBuffer(getServletConfig()
				.getServletContext().getRealPath(""));
		
		if(!StringUtil.isEndsWithFileSysSep(buf.toString())){
			buf.append(separator);
		}
		buf.append("WEB-INF");
		buf.append(separator);
		buf.append("config");
		buf.append(separator);
		buf.append("contributions");
		buf.append(separator);
		buf.append("common");
		buf.append(separator);
		buf.append("env.properties");
		String envFilePath = buf.toString();
		Properties pros = new Properties();
		FileInputStream stream = new FileInputStream(envFilePath);
		try {
			pros.load(stream);
			return pros.getProperty("logPath");
		} finally {
			if (stream != null) {
				stream.close();
			}
		}
	}

	/**
	 * 日志log4j.xml文件的路径，建议旧应用在升级的时候，将日志文件放入此目录下.
	 * 
	 * @return
	 */
	private String getLogFilePath() throws Exception {
		String logVariable = getLogFilePropertiesVariableValue();
		if (logVariable == null) {
			throw new Exception("系统获取日志文件中的变量值为空，请在env.properties文件中设置");
		}
		System.setProperty("logPath", logVariable);

		String separator = System.getProperty("file.separator");
		StringBuffer buf = new StringBuffer(getServletConfig()
				.getServletContext().getRealPath(""));
		if(!StringUtil.isEndsWithFileSysSep(buf.toString())){
			buf.append(separator);
		}
		buf.append("WEB-INF");
		buf.append(separator);
		buf.append("config");
		buf.append(separator);
		buf.append("contributions");
		buf.append(separator);
		buf.append("common");
		buf.append(separator);
		buf.append("log");
		buf.append(separator);
		buf.append("log4j.xml");
		return buf.toString();
	}

	/**
	 * CTP平台初始化.
	 * 
	 * <pre>
	 *   1.平台内部参数和变量复位
	 *   2.log4j日志初始化
	 *   3.初始化dse.ini文件，如果是旧平台则初始化相关的dsesrvce.xml文件，如果是新平台则是
	 *     获取dse.ini文件所在的目录，供一些交易在获取相对目录的时候使用，此Settings.reset
	 *     必不可少.
	 *   4.初始化IOC模块工厂，应用系统自己加载构件包contributions目录下的所有modules.xml结尾的文件
	 * 5.初始化OP，对于新旧平台都需要初始化OP交易，即使该OP交易内容为空.
	 * 
	 * </pre>
	 * 
	 * @throws IOException
	 * @throws CTEException
	 * @throws Exception
	 */
	private synchronized void initSystem() throws IOException, CTEException,
			Exception {
		CTEConstance.reset();
		//修改CTPStartupServlet.java，在initSystem方法调用DOMConfigurator.configure初始化log4j之前，先new file检查文件是否存在，不存在则报错
//		String filepath = getLogFilePath();
//		File file = new File(filepath);
//		if(!file.exists()){
//			throw new Exception("请检查log4j的配置文件（"+filepath+"）是否存在");
//		}
//		
		//configureAndWatch方法可以定时刷新log4j.xml文件，如果有变化，可以重新加载
//		DOMConfigurator.configureAndWatch(filepath, refreshLogConfigInteval);
//		DOMConfigurator.configure(getLogFilePath());
		ctpLog = LogFactory.getCtpLog(CTPStartupServlet.class);
		
		String path = ApplicationContext.getInstance().getCommonHome() + "/" + "env.properties";
		PropertyConfigurer.getInstance().initPors(path);
		
		Settings.reset(getDseFilePath(), isNewProject);
		initIOC(getContributionsPath(), "modules.xml");
		initOpExcute();
	}

	/**
	 * 实例化模块工厂.
	 * 
	 * <pre>
	 * 	如果模块工厂对象已经存在，则销毁原有的模块工厂对象，并重新创建模块工厂对象.
	 * </pre>
	 * 
	 * @param contributionsPath
	 * @param IOC_PREFIX
	 * @throws ModulesException
	 * @throws FactortInitialException
	 * 
	 * @throws ConstructorException
	 */
	private void initIOC(String contributionsPath, String IOC_PREFIX)
			throws ModulesException, FactortInitialException,
			ConstructorException {
		if (appContext != null) {
			try {
				appContext.close();
			} catch (ApplicationContextException e) {
				ctpLog.error("ctpdemo->CTPStartupServlet", "CTP启动－模块工厂对象销毁失败",
						e);
				throw e;
			}
		}

		List<String> paths = new ArrayList<String>();
		File moduledir = new File(contributionsPath);
		String filter = new String(IOC_PREFIX);
		ArrayList<String> fileExtList = new ArrayList<String>(10);
		fileExtList.add(filter);
		File[] defFiles = FileUtil.getInstance().listFileByExt(moduledir,
				fileExtList, true);
		for (File file : defFiles) {
			paths.add(file.getAbsolutePath());
		}

		appContext = FileSystemXmlApplicationContext.getFactory();
		WebApplicationContext.setServletContext(getServletContext());
		if (paths.size() > 0) {
			try {
				appContext.refresh(paths);
			} catch (ModulesException e) {
				ctpLog.error("ctpdemo->CTPStartupServlet", "CTP启动－模块创建失败", e);
				throw e;
			} catch (FactortInitialException e) {
				ctpLog.error("ctpdemo->CTPStartupServlet", "CTP启动－工厂初始化异常", e);
				throw e;
			} catch (ConstructorException e) {
				ctpLog.error("ctpdemo->CTPStartupServlet", "CTP启动－构造函数失败", e);
				throw e;
			}
		} else {
			ctpLog
					.error("ctpdemo->CTPStartupServlet",
							"CTP启动－没有在项目中找到IOC文件（do not find file name ends with modules.xml）");
		}

	}

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		StringBuilder sb = new StringBuilder();
		// 请注意修改此处的编码
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html; charset=utf-8");
		PrintWriter pw = response.getWriter();
		sb
				.append("<HEAD><H1><B>CTPDEMOStartupServlet</B></H1><HR size=0.5></HEAD>");
		sb.append("<BODY>");
		try {
			initSystem();
			try {
				Object obj = FileSystemXmlApplicationContext
						.get("moduleInfoLog");
				if (obj != null && obj instanceof ModuleInfoLogger) {
					sb.append("CTPStartupServlet init ok");
					sb.append("<BR><HR size=0.5>");
					sb.append("<p><a href='" + request.getContextPath()
							+ "/index.jsp'><b>Sign in Page...</b></a></p>");
					sb
							.append("<center><a href=\"index.jsp\">sign page</a></center>");
					sb.append("<pre>");
					if (isNewProject) {
						sb.append("<font color='red'>全新应用系统</font>\r\n");
					} else {
						sb.append("<font color='red'>旧平台升级后的兼容应用系统</font>\r\n");
					}
					sb.append(((ModuleInfoLogger) obj)
							.getIocInfo(FileSystemXmlApplicationContext
									.getFactory()));
				} else {
					sb.append("初始化失败，未在ctp-modules.xml文件中定义moduleInfoLog模块");
				}
			} catch (Exception e) {

			}
			pw.println(sb.toString());
		} catch (Exception e) {
			sb.append("CTPStartupServlet init fail\r\n");
			pw.write(e.toString());
			if (ctpLog != null) {
				ctpLog.error("ctpdemo->CTPStartupServlet", "系统doPost初始化失败", e);
			} else {
				log("系统doPost初始化失败", e);
			}

		}
		pw.println("</BODY></HTML>");

	}

	public void destroy() {
		try {
			if (appContext != null) {
				appContext.close();
				appContext = null;
			}
		} catch (ApplicationContextException e) {
			ctpLog.error("ctpdemo->CTPStartupServlet", "CTP关闭－异常", e);
			log("初始化servlet销毁发生异常" + e.toString());
		}
	}
}